/*----------------------+
|	Dungeon Generator	|
|	-----------------	|
|	Class that is used	|
|	to design random	|
|	dungeon levels		|
+----------------------*/

#pragma region Includes

#include "DungeonGenerator.h"

// Tile engine files
#include "Level.h"
#include "Room.h"
#include "Tile.h"

// Other utility files
#include <stack>

// Access other states
#include "GamePlayState.h"
#include "TutorialState.h"

// Access the boss enemies
#include "SecurityCore.h"
#include "SawBot.h"
#include "ChargeBot.h"
#include "HiveMind.h"

// Level Objects
#include "Chest.h"
#include "PowerBox.h"
#include "FloorSaw.h"
#include "LaserDoor.h"
#include "FloorSpikes.h"
#include "BoomBarrel.h"

#pragma endregion

// Constructor
Dungeon::Dungeon(void) : Tutorial(false)
{ }

// Destructor
Dungeon::~Dungeon(void) { }

// Get the singleton
Dungeon* Dungeon::GetInstance()
{
	static Dungeon instance;
	return &instance;
}

// Generate a level
Level* Dungeon::GenerateLevel(DUNGEON levelToGenerate)
{
	// Create the level to be returned
	Level* dungeon = new Level();

	// Set the dungeon to be generated
	currentDungeon = levelToGenerate;
	roomCount = 0;

	// Set up the map of rooms, They all start out empty
	for( char i = 0; i < 5; ++i )
	{
		for( char j = 0; j < 5; ++j )
			rooms[i][j] = NULL;
	}

	// Keep track of used room indices to keep them all connected
	std::stack<Point> usedIndices;

	// Create a random starting room
	Point p(rand()%5,rand()%5);
	usedIndices.push(p);
	rooms[p.x][p.y] = CreateRoom(p.x,p.y,SPAWN);
	dungeon->AddRoom( rooms[p.x][p.y] );

	// Build rooms until we have reached the desired level size
	int attempts = 0;
	while( roomCount < 10 )
	{
		// Determine if this is the boss room
		ROOMTYPES type = (roomCount == 9) ? BOSS : NORMAL;

		// Double back if it can't seem to build a room
		if( ++attempts > 4  && usedIndices.size() )
		{
			p = usedIndices.top();
			usedIndices.pop();
			attempts = 0;
		}

		// Choose a direction to build in
		switch( rand()%4 )
		{
		case UP: // Try to build a room above
			if( p.y > 0 )
			{
				if( rooms[p.x][p.y-1] == NULL )
				{
					rooms[p.x][p.y-1] = CreateRoom(p.x,p.y-1,type,dungeon);
					dungeon->AddRoom(rooms[p.x][p.y-1]);
					rooms[p.x][p.y-1]->Openings |= Room::DOWN;
					rooms[p.x][p.y]->Openings |= Room::UP;
					LockRoom(p.x,p.y,rooms[p.x][p.y],UP);
					p.y--;
					usedIndices.push(p);
					attempts = 0;
				}
			}
			break;
		case DOWN: // Try to build a room above
			if( p.y < 4 )
			{
				if( rooms[p.x][p.y+1] == NULL )
				{
					rooms[p.x][p.y+1] = CreateRoom(p.x,p.y+1,type,dungeon);
					dungeon->AddRoom(rooms[p.x][p.y+1]);
					rooms[p.x][p.y+1]->Openings |= Room::UP;
					rooms[p.x][p.y]->Openings |= Room::DOWN;
					LockRoom(p.x,p.y,rooms[p.x][p.y],DOWN);
					p.y++;
					usedIndices.push(p);
					attempts = 0;
				}
			}
			break;
		case LEFT: // Try to build a room above
			if( p.x > 0 )
			{
				if( rooms[p.x-1][p.y] == NULL )
				{
					rooms[p.x-1][p.y] = CreateRoom(p.x-1,p.y,type,dungeon);
					dungeon->AddRoom(rooms[p.x-1][p.y]);
					rooms[p.x-1][p.y]->Openings |= Room::RIGHT;
					rooms[p.x][p.y]->Openings |= Room::LEFT;
					LockRoom(p.x,p.y,rooms[p.x][p.y],LEFT);
					p.x--;
					usedIndices.push(p);
					attempts = 0;
				}
			}
			break;
		case RIGHT: // Try to build a room above
			if( p.x < 4 )
			{
				if( rooms[p.x+1][p.y] == NULL )
				{
					rooms[p.x+1][p.y] = CreateRoom(p.x+1,p.y,type,dungeon);
					dungeon->AddRoom(rooms[p.x+1][p.y]);
					rooms[p.x+1][p.y]->Openings |= Room::LEFT;
					rooms[p.x][p.y]->Openings |= Room::RIGHT;
					LockRoom(p.x,p.y,rooms[p.x][p.y],RIGHT);
					p.x++;
					usedIndices.push(p);
					attempts = 0;
				}
			}
			break;
		}
	}

	// Fill in all the walls/hallways
	for( int i = 0; i < 5; ++i )
	{
		for( int j = 0; j < 5; ++j )
		{
			if( rooms[i][j] != NULL )
				FillRoomWalls(rooms[i][j],i,j);
		}
	}

	dungeon->SetRoomCount(10);

	// Return the dungeon built
	return dungeon;
}

// Create rooms
Room* Dungeon::CreateRoom(int x, int y, ROOMTYPES type, Level* l)
{
	Room* room = NULL;

	// Set the room difficulty based on the current dungeon
	int difficulty = 30;
	difficulty += 5 * currentDungeon;

	// Create the new room, and increase the room counter
	room = new Room(difficulty,roomCount);

	// Create the rooms tiles : 23 x 15
	for( char i = 0; i < 23; ++i )
	{
		for( char j = 0; j < 15; ++j )
		{
			// Create walls
			if( i == 0 || i == 22 || j == 0 || j == 14 )
			{
				if( !((i > 9 && i < 13) || (j > 5 && j < 9)) )
				{
					RECT image = {0,0,32,32};
					if( j == 0 || j == 14 )
					{
						image.right = 32 + (image.left += 32);
					}

					room->AddTile( CreateTile(TILE_COLLISION,i*32 + x*800 + 32,j*32 + y*600 + 64,nullptr) );
					room->AddTile( CreateTile(TILE_IMAGE,i*32+x*800 + 32,j*32+y*600 + 64,&image) );
				}
			}
			// Create floors here if we need them
		}
	}

	// Create enemies
	if( type == NORMAL )
	{
		room->AddTile( CreateTile(TILE_SPAWN,400 + 800*x,300 + 600*y,nullptr) );
		int enemies = 3;
		if( currentDungeon == DUNGEON_POWERPLANT )
			enemies = 5;
		if( currentDungeon == DUNGEON_MILITARYBASE || currentDungeon == DUNGEON_RECYCLINGCENTER)
			enemies = 6;
		room->AddEnemy(enemies);

		// Maybe build a chest
		if( rand()%4 == 3)
			GamePlayState::GetInstance()->AddChest(SpawnChest(x,y));
		//else if( rand()%4 == 3 && Tutorial == false)
		//	TutorialState::GetInstance()->AddChest(SpawnChest(x,y));

		// Spawn level Hazards
		if( true /*&& Tutorial == false*/)//rand()%4 == 3 )
			GamePlayState::GetInstance()->AddHazard(CreateHazard(x,y));
		//if( true && Tutorial == true)//rand()%4 == 3 )
		//	TutorialState::GetInstance()->AddHazard(CreateHazard(x,y));
	}

	// Spawn the player in the first room
	if( type == SPAWN)
	{
		GamePlayState::GetInstance()->GetPlayer()->SetX(x*800.0f + 200);
		GamePlayState::GetInstance()->GetPlayer()->SetY(y*600.0f + 200);
		GamePlayState::GetInstance()->SetCam(x*800,y*600);
		room->spawned = true;
	}
	//else if(type == SPAWN && Tutorial == true)
	//{
	//	TutorialState::GetInstance()->GetPlayer()->SetX(x*800.0f + 200);
	//	TutorialState::GetInstance()->GetPlayer()->SetY(y*600.0f + 200);
	//	TutorialState::GetInstance()->SetCam(x*800,y*600);
	//	room->spawned = true;
	//}

	// Spawn the boss in the boss room
	if( type == BOSS && Tutorial == false )
	{
		//SpawnBoss(x,y,l);
		room->AddEnemy(SpawnBoss(x,y,l));
		room->spawned = true;
	}

	++roomCount;

	return room;
}

// Create individual tiles
Tile* Dungeon::CreateTile(TileTypes tiletype, int x, int y, RECT* imageRect, char* trigger)
{
	Tile* tile = NULL;

	if( imageRect == nullptr )
	{
		RECT r = {0,0,0,0};
		imageRect = &r;
	}

	tile = new Tile(tiletype,x,y,32,32,trigger,roomCount,imageRect->left,imageRect->top,imageRect->right,imageRect->bottom);

	return tile;
}

// Build a hall to connect rooms
void Dungeon::CreateHall(Side wall, Room* room, int roomx, int roomy)
{
	RECT image = {0,0,32,32};

	switch( wall )
	{
	case UP: // Build the upper hall
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 320,roomy*600,&image) );
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 448,roomy*600,&image) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 320,roomy*600,nullptr) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 448,roomy*600,nullptr) );
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 320,roomy*600 + 32,&image) );
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 448,roomy*600 + 32,&image) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 320,roomy*600 + 32,nullptr) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 448,roomy*600 + 32,nullptr) );
		break;
	case DOWN: // Build the lower hall
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 320,roomy*600 + 384 + 128 + 64,&image) );
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 448,roomy*600 + 384 + 128 + 64,&image) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 320,roomy*600 + 384 + 128 + 64,nullptr) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 448,roomy*600 + 384 + 128 + 64,nullptr) );
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 320,roomy*600 + 416 + 128,&image) );
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 448,roomy*600 + 416 + 128,&image) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 320,roomy*600 + 416 + 128,nullptr) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 448,roomy*600 + 416 + 128,nullptr) );
		break;
		break;
	case LEFT: // Build the left hall
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800,roomy*600 + 224,&image) );
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800,roomy*600 + 352,&image) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800,roomy*600 + 224,nullptr) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800,roomy*600 + 352,nullptr) );
		break;
	case RIGHT: // Build the right hall
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 768,roomy*600 + 224,&image) );
		room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 768,roomy*600 + 352,&image) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 768,roomy*600 + 224,nullptr) );
		room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 768,roomy*600 + 352,nullptr) );
		break;
	}
}

// Fill in a wall
void Dungeon::CreateWall(Side wall, Room* room, int roomx, int roomy)
{
	RECT image = {0,0,32,32};

	switch( wall )
	{
	case UP: // Close the top door
		for( int i = 9; i < 14; ++i)
		{
			room->AddTile( CreateTile(TILE_COLLISION,i*32 + roomx*800 + 32,roomy*600 + 64,nullptr) );
			room->AddTile( CreateTile(TILE_IMAGE,i*32+roomx*800 + 32,roomy*600 + 64,&image) );
		}
		break;
	case DOWN: // Close the bottom door
		for( int i = 9; i < 14; ++i)
		{
			room->AddTile( CreateTile(TILE_COLLISION,i*32 + roomx*800 + 32,roomy*600 + 512,nullptr) );
			room->AddTile( CreateTile(TILE_IMAGE,i*32+roomx*800 + 32,roomy*600 + 512,&image) );
		}
		break;
	case LEFT: // Close the left door
		for( int i = 7; i < 10; ++i)
		{
			room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 32,roomy*600 + 32*i + 32,nullptr) );
			room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 32,roomy*600 + 32*i + 32,&image) );
		}
		break;
	case RIGHT: // Close the right door
		for( int i = 7; i < 10; ++i)
		{
			room->AddTile( CreateTile(TILE_COLLISION,roomx*800 + 736,roomy*600 + 32*i + 32,nullptr) );
			room->AddTile( CreateTile(TILE_IMAGE,roomx*800 + 736,roomy*600 + 32*i + 32,&image) );
		}
		break;
	}
}

// Fill in the walls where there are no doors
void Dungeon::FillRoomWalls(Room* room, int roomx, int roomy)
{
	// Fill in walls
	if( !(room->Openings & Room::LEFT) )
		CreateWall(LEFT,room,roomx,roomy);
	else
		CreateHall(LEFT,room,roomx,roomy);
	if( !(room->Openings & Room::RIGHT) )
		CreateWall(RIGHT,room,roomx,roomy);
	else
		CreateHall(RIGHT,room,roomx,roomy);
	if( !(room->Openings & Room::UP) )
		CreateWall(UP,room,roomx,roomy);
	else
		CreateHall(UP,room,roomx,roomy);
	if( !(room->Openings & Room::DOWN) )
		CreateWall(DOWN,room,roomx,roomy);
	else
		CreateHall(DOWN,room,roomx,roomy);
}

// Spawn a chest
Chest* Dungeon::SpawnChest(int roomx, int roomy)
{
	Chest* c = new Chest();
	c->SetX(32*(rand()%13 + 5) + 800.0f*roomx);
	c->SetY(32*(rand()%8 + 5) + 600.0f*roomy);
	return c;
}

// Spawn a hazard
Hazard* Dungeon::CreateHazard(int roomx, int roomy)
{
	Hazard* h = nullptr;

	// Determine a random location to place it
	int x = 32*(rand()%10 + 5) + 800*roomx;
	int y = 32*(rand()%10 + 5) + 600*roomy;

	// Create the hazard based on the dungeon
	switch( currentDungeon )
	{
	case DUNGEON_RESEARCHLAB:
		h = new BoomBarrel();
		h->SetX((float)x);
		h->SetY((float)y);
		h->SetRoomNumber(roomCount);
		break;
	case DUNGEON_MILITARYBASE:
		h = new FloorSpikes();
		h->SetX((float)x);
		h->SetY((float)y);
		h->SetRoomNumber(roomCount);
		break;
	case DUNGEON_POWERPLANT:
		h = new PowerBox();
		h->SetX((float)x);
		h->SetY((float)y);
		h->SetRoomNumber(roomCount);
		break;
	case DUNGEON_RECYCLINGCENTER:
		h = new FloorSaw();
		h->SetX((float)x);
		h->SetY((float)y);
		((FloorSaw*)(h))->Activate((rand()%2==1));
		h->SetRoomNumber(roomCount);
		break;
	}

	return h;
}

// Spawn the level's boss
Enemy* Dungeon::SpawnBoss(int x, int y, Level* l)
{
	Enemy* e = nullptr;

	// Spawn the dungeon's boss
	switch( currentDungeon )
	{
	case DUNGEON_RESEARCHLAB:
		{
			// Spawn the core
			int tmpID[2];
			for(int j = 0; j < 2; ++j)
			{
				Animation* tmpAnimation = new Animation();
				tmpAnimation->LoadAnimationFromFile("CoreAnimation.xml", j);
				tmpID[j] = AnimationSystem::GetInstance()->AddAnimation(tmpAnimation);
			}
			//rooms[x][y]->AddTile( CreateTile(TILE_SPAWN,x*800+128,y*600+128,nullptr) );
			SecurityCore* core = new SecurityCore();
			core->SetX(x*800 + 300.0f);
			core->SetY(y*600 + 300.0f);
			core->SetRoomNum(roomCount);
			core->SetMyRoom(l->GetRoomVec());
			core->SetAnimation(tmpID);
			//rooms[x][y]->AddEnemy(core);
			//ObjectManager::GetInstance()->AddObject(core);
			e = (Enemy*)core;
		}
		break;
	case DUNGEON_POWERPLANT:
		{
			// Spawn the charge bot
			int tmpID[8];
			for(int j = 0; j < 8; ++j)
			{
				Animation* tmpAnimation = new Animation();
				tmpAnimation->LoadAnimationFromFile("ChargeAnimation.xml", j);
				tmpID[j] = AnimationSystem::GetInstance()->AddAnimation(tmpAnimation);
			}
			//rooms[x][y]->AddTile( CreateTile(TILE_SPAWN,x*800+128,y*600+128,nullptr) );
			ChargeBot* charger = new ChargeBot();
			charger->SetX(x*800 + 300.0f);
			charger->SetY(y*600 + 300.0f);
			charger->SetRoomNum(roomCount);
			charger->SetAnimation(tmpID);
			//rooms[x][y]->AddEnemy((Enemy*)charger);
			//ObjectManager::GetInstance()->AddObject(charger);
			--roomCount;
			GamePlayState::GetInstance()->AddHazard(CreateHazard(x,y));
			GamePlayState::GetInstance()->AddHazard(CreateHazard(x,y));
			GamePlayState::GetInstance()->AddHazard(CreateHazard(x,y));
			GamePlayState::GetInstance()->AddHazard(CreateHazard(x,y));
			++roomCount;
			e = (Enemy*)charger;
		}
		break;
	case DUNGEON_MILITARYBASE:
		{
			// Spawn the Hive Mind
			int tmpID[8];
			for(int j = 0; j < 8; ++j)
			{
				Animation* tmpAnimation = new Animation();
				tmpAnimation->LoadAnimationFromFile("HiveAnimation.xml", j);
				tmpID[j] = AnimationSystem::GetInstance()->AddAnimation(tmpAnimation);
			}
			//rooms[x][y]->AddTile( CreateTile(TILE_SPAWN,x*800+128,y*600+128,nullptr) );
			HiveMind* hive = new HiveMind();
			hive->SetX(x*800 + 300);
			hive->SetY(y*600 + 300);
			hive->SetRoomNum(roomCount);
			hive->SetAnimation(tmpID);
			//rooms[x][y]->AddEnemy(hive);
			//ObjectManager::GetInstance()->AddObject(hive);
			e = (Enemy*)hive;
		}
		break;
	case DUNGEON_RECYCLINGCENTER:
		{
			GamePlayState* game = GamePlayState::GetInstance();
			// Spawn the Saw Bot
			int idR[4];
			for(int i = 0; i < 4; ++i)
			{
				Animation* tmp = new Animation;
				tmp->LoadAnimationFromFile("SawBot.xml", i);
				idR[i] = AnimationSystem::GetInstance()->AddAnimation(tmp);
			}
			//rooms[x][y]->AddTile( CreateTile(TILE_SPAWN,x*800+128,y*600+128,nullptr) );
			SawBot* sawBot = new SawBot(game->GetPlayer(), game->GetPlayer2());
			sawBot->SetX(x*800 + 300.0f);
			sawBot->SetY(y*600 + 100.0f);
			sawBot->SetRoomNum(roomCount);
			//sawBot->SetMyRoom(dungeon->GetRoomVec());
			sawBot->SetAnimationID( idR );
			//rooms[x][y]->AddEnemy(sawBot);
			//ObjectManager::GetInstance()->AddObject(sawBot);
			e = (Enemy*)sawBot;
		}
		break;
	}

	return e;
}

// Create a lock for the room
void Dungeon::LockRoom(int roomx, int roomy, Room* roomToLock, int side)
{
	if( !roomToLock )
		return;

	LaserDoor* door = nullptr;

	switch( side )
	{
	case UP:
		door = new LaserDoor(roomToLock,false);
		door->SetX(roomx*800+352);
		door->SetY(roomy*600+64);
		break;
	case DOWN:
		door = new LaserDoor(roomToLock,false);
		door->SetX(roomx*800+352);
		door->SetY(roomy*600+512);
		break;
	case LEFT:
		door = new LaserDoor(roomToLock,true);
		door->SetX(roomx*800+32);
		door->SetY(roomy*600+256);
		break;
	case RIGHT:
		door = new LaserDoor(roomToLock,true);
		door->SetX(roomx*800+736);
		door->SetY(roomy*600+256);
		break;
	}

	GamePlayState::GetInstance()->Doors.push_back(door);

	ObjectManager::GetInstance()->AddObject(door);
	door->Release();
}

// Build the tutorial level
Level* Dungeon::GenerateTutorial(void)
{
	Tutorial = true;
	Level* l = GenerateLevel(DUNGEON_RESEARCHLAB);
	Tutorial = false;
	return l;
}